#include "clientoperation.h"
#include "jsonparse.h"
#include "requestfactory.h"
#include "tcpsocket.h"
#include "respondfactory.h"
#include "aescrypto.h"
#include <QCryptographicHash>
#include <QDebug>
#include <QThread>


ClientOperation::ClientOperation(QObject *parent) : QObject(parent)
{
    // 实例化共享内存对象
    CliConfig conf = JsonParse::getInstance()->readClientConfig();
    m_shm = new SeckeyShm(conf.shmKey, 1);
}

void ClientOperation::consultPrepare(RequestInfo& info, RsaCrypto &rsa, ClientOperation* cliop)
{
    Q_UNUSED(cliop)
    info.cmdtype = CmdType::Consult;
    info.data = rsa.publicPemKey();   // 公钥字符串
}

void ClientOperation::consultDispose(RespondInfo& resInfo, ClientOperation* cliop)
{
    if(resInfo.status)
    {
        emit cliop->sendMsg("服务器处理秘钥协商成功了...");
        // 解析接收的data中的数据
        // {"KeyID":"xxx", "AesKey":"xxx"}
        QString keyid = JsonParse::getInstance()->simpleObjValue(resInfo.data, "KeyID");
        QString aeskey = JsonParse::getInstance()->simpleObjValue(resInfo.data, "AesKey");
        // 使用私钥解密数据
        // 密钥对是在客户端上生成的, 在磁盘文件中有持久化的秘钥信息
        RsaCrypto rsa;
        QString pripem = rsa.privatePemKey();    // 读出私钥文件中的pem字符串
        // // 解析私钥字符串还原数据到rsa对象中, rsa对象中有了可用的私钥
        rsa.parseStringToKey(pripem, RsaCrypto::Private);
        QString key = rsa.rsaPriKeyDecrypt(aeskey);
        qDebug() << "对称加密秘钥: " << key;
        emit cliop->sendMsg("秘钥协商的秘钥: " + key + ", keyID: " + keyid);
        // 写入到秘钥协商客户端的共享内存中
        CliConfig conf = JsonParse::getInstance()->readClientConfig();
        SeckeyInfo keyinfo(true, keyid.toInt(), conf.clientID, conf.serverID, key);
        cliop->m_shm->writeShm(keyinfo);
        qDebug() << "写共享内存成功, 秘钥为: " << key;
        emit cliop->sendMsg("秘钥协商的秘钥被写入到共享内存中...");
        emit cliop->sendMsg("秘钥协商成功了, 哈哈...");

        ///////////////////////////////////////////////////
        /////////  生成外联接口需要的配置文件 json    /////////
        // 准备数据
        QStringList keys, values;
        keys << "ShmKey" << "ServerID" << "ClientID";
        values << conf.shmKey << conf.serverID << conf.clientID;
        JsonParse::getInstance()->writeInterfaceJson(keys, values);
    }
    else
    {
        emit cliop->sendMsg("秘钥协商失败了, 呜呜...");
    }
}

void ClientOperation::verifyPrepare(RequestInfo &info, RsaCrypto &rsa, ClientOperation* cliop)
{
    info.cmdtype = CmdType::Verify;
    // {"PubKey":xxx, "Hash":"yyyyz"}
    JsonParse* json = JsonParse::getInstance();
    QStringList keys, values;
    keys << "PubKey" << "Hash";
    CliConfig conf = JsonParse::getInstance()->readClientConfig();
    SeckeyInfo keyinfo = cliop->m_shm->readShm(conf.clientID, conf.serverID);
    QString sha224 = QCryptographicHash::hash(
                keyinfo.seckey, QCryptographicHash::Sha224);
    values << rsa.publicPemKey() << sha224;
    info.data = json->generateSimpleObj(keys, values);   // json字符串, 有两部分数据
}

void ClientOperation::verifyDispose(RespondInfo &resInfo, ClientOperation *cliop)
{
    if(resInfo.status)
    {
        emit cliop->sendMsg("秘钥校验成功了, 哈哈...");
    }
    else
    {
        emit cliop->sendMsg("秘钥校验失败了, 呜呜...");
    }
}

void ClientOperation::logoutPrepare(RequestInfo &info, RsaCrypto &rsa, ClientOperation *cliop)
{
    info.cmdtype = CmdType::Logout;
    CliConfig conf = JsonParse::getInstance()->readClientConfig();
    SeckeyInfo keyInfo = cliop->m_shm->readShm(conf.clientID, conf.serverID);
    QStringList keys, values;
    keys << "KeyID" << "Status" << "PubKey";
    values << QString::number(keyInfo.seckeyID)
           << QString::number(!keyInfo.status)
           << rsa.publicPemKey();
    info.data = JsonParse::getInstance()->generateSimpleObj(keys, values);
}

void ClientOperation::logoutDispose(RespondInfo &resInfo, ClientOperation *cliop)
{
    if(resInfo.status)
    {
        // 修改秘钥协商客户端共享内存中秘钥状态
        CliConfig conf = JsonParse::getInstance()->readClientConfig();
        SeckeyInfo keyInfo = cliop->m_shm->readShm(conf.clientID, conf.serverID);
        keyInfo.status = !keyInfo.status;
        cliop->m_shm->writeShm(keyInfo);
        emit cliop->sendMsg("秘钥状态修改成功, 恭喜...");
    }
    else
    {
         emit cliop->sendMsg("秘钥状态修改失败, 非常遗憾...");
    }
}

void ClientOperation::loginPrepare(RequestInfo &info, RsaCrypto &rsa, ClientOperation *cliop)
{
    info.cmdtype = Login;
    // {"NetID":"xxx", "Password":"xxxx", "PubKey":"xxxx"}
    // 准备数据
    QStringList keys, values;
    keys << "NetID" << "Password" << "PubKey";
    values << cliop->m_netID << cliop->m_passwd << rsa.publicPemKey();
    info.data = JsonParse::getInstance()->generateSimpleObj(keys, values);

    // 网点的ID和密码是明文的方式发送给了服务器, 数据容易泄露, 可以对这些数据进行加密
    // 使用对称加密的方式, 对称加密的秘钥 -> 从共享内存中读出 -> 秘钥协商得到的
    // 在登录之前, 需要先做一次秘钥协商, 得到对称加密的秘钥, 再使用这个秘钥对登录数据加密
    // 从共享内存中读秘钥
    SeckeyInfo keyinfo = cliop->m_shm->readShm(info.clientID, info.serverID);
    AesCrypto aes(keyinfo.seckey);
    info.data = aes.aesCBCEncrypt(info.data);
}

void ClientOperation::loginDispose(RespondInfo &resInfo, ClientOperation *obj)
{
    if(resInfo.status)
    {
        // 可以登录
        emit obj->loginSuccess();
    }
    else
    {
        // 不能登录
        emit obj->loginFail();
    }
}

void ClientOperation::communication(void *arg)
{
    // arg 实际上是 ArgInfo 类型的对象
    ArgInfo * arginfo = (ArgInfo*)arg;
    // 将传递进来的类实例对象类型进行转换
    ClientOperation* cliop = arginfo->object;

    // 1. 生成非对称加密的密钥对
    RsaCrypto rsa;
    rsa.generateRsakey(2048);   // 秘钥长度 2048bit
    // 发送信号的时候 emit是可以不加的, 对于编译器来说加不加是一样的
    // define emit
    emit cliop->sendMsg("生成秘钥校验需要的密钥对...");

    // 2. 初始化要发送的数据
    RequestInfo info;
    // 登录的时候配置信息已经写入到配置文件中了, 直接读即可
    CliConfig conf = JsonParse::getInstance()->readClientConfig();
    info.clientID = conf.clientID;
    info.serverID = conf.serverID;

    ////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////
    // 不同的请求, 需要准备不同的数据, 进行函数调用(通过函数指针)
    arginfo->prepare(info, rsa, cliop);

    ////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////
    // rsa自带的签名函数不能签名太长的数据, 如果超过秘钥长度之后, 签名会有问题
    // info.sign = rsa.rsaSign(info.data);   // 对data签名
    // 解决方案: 先计算 info.data 的哈希, 然后在对得到的哈希进行签名
    //          校验的时候, 安装这种模式进行校验就ok
    QByteArray sha1 = QCryptographicHash::hash(
                info.data.toUtf8(), QCryptographicHash::Sha1);
    info.sign = rsa.rsaSign(sha1);
    emit cliop->sendMsg("秘钥协校验据准备完成...");

    // 3. 序列化要发送的数据
    CodecFactory* f = new RequestFactory(&info);
    Codec* c = f->createCodec();
    // 序列化, 得到要发送的数据字符串
    QString encStr = c->encodeMsg();
    // 释放资源
    delete c;
    delete f;
    emit cliop->sendMsg("秘钥校验数据序列化完成...");

    // 4. 客户端要发送数据, 需要先进行服务器连接
    TcpSocket* tcp  = new TcpSocket;
    do{
        if(tcp == NULL)
        {
            qDebug() << "创建套接字对象失败, 拜拜...";
            emit cliop->sendMsg("创建套接字对象失败, 拜拜...");
            break;
        }
        // 连接服务器
        int ret = tcp->connectToHost(conf.ip, conf.port.toInt());
        if(ret != 0)
        {
            qDebug() << "客户端连接服务器失败了...";
            emit cliop->sendMsg("客户端连接服务器失败了...");
            break;
        }
        qDebug() << "客户端连接服务器成功了, 恭喜!!!";
        emit cliop->sendMsg("客户端连接服务器成功了, 恭喜!!!");
        // 发送数据给服务器
        tcp->sendMsg(encStr);
        emit cliop->sendMsg("秘钥校验请求成功发送给了服务器...");

        // 阻塞接收服务器回复的数据
        QString recvStr = tcp->recvMsg();
        emit cliop->sendMsg("收到服务器回复的校验信息...");

        // 反序列化接收的服务器数据
        f = new RespondFactory(recvStr);
        c = f->createCodec();
        RespondInfo resInfo;
        c->decodeMsg(&resInfo);

        qDebug() << "服务器处理结果: " << resInfo.status;
        // 判断状态
        ////////////////////////////////////////////////
        // 请求不同, 收到服务器回复的数据之后, 进行不同的处理
        // 使用函数指针, 调用对应的功能函数
        arginfo->dispose(resInfo, cliop);
        ////////////////////////////////////////////////
    }while(0);


    // 释放资源
    // 套接字断开连接
    if(tcp)
    {
        tcp->disConnect();
        delete tcp;
    }
    if(f) delete f;
    if(c) delete c;
    // 释放参数指向的内存
    delete arginfo;
}

void ClientOperation::lastTask(void *arg)
{
    // 让子线程工作, 保证线程不被条件变量阻塞
    // 让线程睡觉(这个阻塞和被条件变量阻塞是两码事儿)
    QThread::sleep(3);
}

void ClientOperation::saveLoginInfo(QString id, QString pwd)
{
    m_netID = id;
    m_passwd = pwd;
}

